/*
 * Copyright 2014 JootMir Project
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * Support: http://www.jootmir.org
 */
package org.jootmir.client.core;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.MalformedURLException;
import java.net.URL;

import org.apache.log4j.Logger;
import org.jootmir.client.core.exception.JMir2Exception;

/**
 * 文件读写相关工具类
 * <br>
 * 程序会根据你提供的路径(不包括诸如'jar:','file:','http:'等协议前缀)查找文件
 * <br>
 * 程序会先在程序运行文件夹及其子文件夹下查找文件，然后再从classpath(包括各个jar包内)查找文件，最后会将你给出的路径当作文件系统绝对路径或网络位置进行处理
 * <br>
 * 修改搜索方式优先级请更改{@link #SEARCH_TYPE_STRING searchType}属性
 * 
 * @author ShawRyan
 */
public final class Files {

	/** log */
	private static final Logger log = Logger.getLogger(Files.class);
	/** 缓存 */
	private static AllCache cache = AllCache.get();
	
	/** 默认搜索优先级字符串 */
	private static final String SEARCH_TYPE_STRING = "E,I,A,N";
	/** 搜索方式，程序会按此数组作为搜索方式优先级序列 */
	private static SearchType[] searchTypes = SearchType.values();
	
	/** 文件搜索方式 */
	private static enum SearchType {
		/** 在classpath或jar包内搜索 */
		I,
		/** 在程序启动时的文件夹内及其子文件夹中搜索 */
		E,
		/** 将文件路径当作文件系统绝对路径处理 */
		A,
		/** 将给定路径作为网络地址处理 */
		N
	}
	
	static {
		String[] sTypes = SEARCH_TYPE_STRING.split(",");
		searchTypes = new SearchType[searchTypes.length];
		int i = 0;
		for(String searchType : sTypes) {
			if(searchType.equals("I")) {
				searchTypes[i++] = SearchType.I;
			} else if(searchType.equals("E")) {
				searchTypes[i++] = SearchType.E;
			} else if(searchType.equals("A")) {
				searchTypes[i++] = SearchType.A;
			} else if(searchType.equals("N")) {
				searchTypes[i++] = SearchType.N;
			}
		}
	}
	
	/**
	 * 根据给定文件路径查找文件
	 * <br>
	 * 给定路径时请使用‘/’作为文件夹分隔符
	 * <br>
	 * 请给定全路径，例如您希望获取获取的文件是com.example.a包下的b.txt，请使用com/example/a/b.txt
	 * <br>
	 * 程序会自行处理您给定路径中的首个字符，您可以自行决定加入或取消首个字符的'/'
	 * 
	 * XXX <b>不支持</b>从jar包内读取文件，使用{@link #inputStream(String)}直接获取流
	 * 
	 * @param path
	 * 		路径
	 * @return 搜寻得到的文件
	 */
	public static File file(String path) {
		assert path != null : "parameter 'path' can not be null!";
		for(SearchType searchType : searchTypes) {
			if(searchType == SearchType.I) {
				String realPath = internal(path);
				if(realPath != null) {
					File file = new File(realPath);
					if(file.exists()) return file;
				}
			} else if(searchType == SearchType.E) {
				String realPath = external(path);
				if(realPath != null) {
					File file = new File(realPath);
					if(file.exists()) return file;
				}
			} else if(searchType == SearchType.A || searchType == SearchType.N) {
				File file = new File(path);
				if(file.exists()) return file;
			}
		}
		return null;
	}
	
	/**
	 * 根据给定路径获取文件读取流
	 * <br>
	 * 您需要自行关闭该流
	 * <br>
	 * 建议您使用{@link #file(String) file}，因为此方法内部也是封装了对其调用
	 * 
	 * @param path
	 * 		路径
	 * @return 该文件读取流
	 */
	public static InputStream inputStream(String path) {
		File file = file(path);
		// 对jar包内文件特殊处理
		// jar包内文件无法通过file方法读出
		if(file == null) {
			if(path.startsWith("/"))
				path = path.substring(1);
			return Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
		}
		
		if(!file.exists()) return null;
		try {
			return new FileInputStream(file);
		} catch (FileNotFoundException e) {
			JMir2Exception exception = JMir2Exception.valueOf(e, JMir2Exception.EXCEPTION_TYPE_CALC);
			log.error(exception.getMessage(), exception);
			throw exception;
		}
	}
	
	/**
	 * 根据给定路径获取文件随机读取流
	 * <br>
	 * 该流默认只读，您需要自行关闭该流
	 * <br>
	 * 建议您使用{@link #file(String) file}，因为此方法内部也是封装了对其调用
	 * <b>为了避免文件指针冲突，在返回流之前函数会自行将文件指针归零{@link RandomAccessFile#seek(long) seek}</b>
	 * 
	 * @param path
	 * 		路径
	 * @return 该路径文件随机访问流
	 */
	@SuppressWarnings("deprecation")
	public static RandomAccessFile randomAccessFile(String path) {
		try {
			// 尝试从缓存的wil/wix文件指针中找
			if(cache.getRandomAccessFile(path) != null){
				RandomAccessFile raf = cache.getRandomAccessFile(path);
				if(raf.getFilePointer() != 0)
					raf.seek(0);
				return raf;
			}
			File file = file(path);
			if(file == null) return null;
			else {
				RandomAccessFile raf = new RandomAccessFile(file, "r");
				cache.putRandomAccessFile(path, raf);
				return raf;
			}
		} catch (IOException e) {
			JMir2Exception exception = JMir2Exception.valueOf(e, JMir2Exception.EXCEPTION_TYPE_CALC);
			log.error(exception.getMessage(), exception);
			throw exception;
		}
	}
	
	/**
	 * 从classpath或jar包内搜索制定路径
	 * 
	 * @param path
	 * 		路径
	 * @return 以URL.getFile()得到的文件路径
	 */
	private static String internal(String path) {
		if(path.startsWith("/"))
			path = path.substring(1);
		URL url = Thread.currentThread().getContextClassLoader().getResource(path);
		return url == null ? null : url.getFile();
	}
	
	/**
	 * 在程序启动时的文件夹内及其子文件夹中搜索
	 * 
	 * @param path
	 * 		路径
	 * @return 以"file:/"开头的路径
	 */
	private static String external(String path) {
		if(!path.startsWith("/"))
			path = "/" + path;
		URL url = null;
		try {
			url = new URL("file:/" + System.getProperty("user.dir") + path);
		} catch (MalformedURLException e) {
			JMir2Exception exception = JMir2Exception.valueOf(e, JMir2Exception.EXCEPTION_TYPE_CALC);
			log.error(exception.getMessage(), exception);
			throw exception;
		}
		return url == null ? null : url.getFile();
	}
}